<?php
/**
 * @see Alinks_Keyphrase_Interface
 */
require_once('Alinks/Keyphrase/Interface.php');

/**
 * Abstract class for keyphrase classes
 *
 * Copyright (C) 2008  Sean Hickey
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Sean Hickey <sean@headzoo.com>
 * @version $Id: Abstract.php 425 2008-07-09 16:27:54Z sean $
 * @package Alinks_Keyphrase
 */
abstract class Alinks_Keyphrase_Abstract
	implements Alinks_Keyphrase_Interface, SplSubject
{
	const IDX_PHRASE		= 'phrase';
	const IDX_DESCRIPTION	= 'description';
	const IDX_OPTIONS		= 'options';
	const IDX_FACTORY		= 'factory';
	const IDX_CREATED		= 'created';
	const IDX_HASH			= 'hash';
	
	/**
	 * Max character lengths
	 */
	const MAX_LENGTH_PHRASE			= 250;
	const MAX_LENGTH_DESCRIPTION	= 450;
	
	/**
	 * The keyphrase data
	 *
	 * @var array
	 */
	protected $_data = array(
		self::IDX_PHRASE		=> null,
		self::IDX_DESCRIPTION	=> null,
		self::IDX_OPTIONS		=> array(),
		self::IDX_FACTORY		=> null,
		self::IDX_CREATED		=> 0,
		self::IDX_HASH			=> null
	);
	
	/**
	 * SplObserver instances watching this instance
	 *
	 * @var SplObserver[]
	 */
	private $__observers = array();
	
	/**
	 * Constructor
	 *
	 * @param string $factory Safe name of the factory the keyphrase belongs to
	 * @param array $data The keyphrase data
	 */
	public function __construct($factory, array $data = array())
	{
		$this->setFactory($factory);
		$this->setData($data);
	}
	
	/**
	 * __sleep
	 */
	public function __sleep()
	{
		return array('_data');
	}
	
	/**
	 * Sets the keyphrase data from an array
	 *
	 * @param array $data The keyphrase data
	 * @return Alinks_Keyphrase_Interface
	 */
	public function setData(array $data)
	{
		foreach($data as $key => $value) {
			switch($key) {
				case self::IDX_PHRASE:
					$this->setPhrase($value);
					break;
				case self::IDX_DESCRIPTION:
					$this->setDescription($value);
					break;
				case self::IDX_OPTIONS:
					$this->setOptions($value);
					break;
				case self::IDX_FACTORY:
					$this->setFactory($value);
					break;
				case self::IDX_CREATED:
					$this->setCreated($value);
					break;
				case self::IDX_HASH:
					$this->setHash($value);
					break;
			}
		}
	}
	
	/**
	 * Returns the phrase
	 *
	 * @return string
	 */
	public function getPhrase()
	{
		return $this->_data[self::IDX_PHRASE];
	}
	
	/**
	 * Returns the phrase as a regular expression
	 *
	 * Phrases can contain wildcard characters. This method converts any of
	 * those into regular expressions.
	 *
	 * @return string
	 */
	public function getPhraseRegexp()
	{
		$phrase = $this->getPhrase();
		if (strstr($phrase, '*') === false) {
			return $phrase;
		}
		
		$clean_phrase = str_replace('*', '', $phrase);
		$phrase_parts = explode(str_replace('*', '', $phrase), $phrase);

		if (isset($phrase_parts[0])) {
			$phrase_parts[0] = preg_replace('/[*]/', '[\\w\'&;\\#!.?\\-"]+ ', $phrase_parts[0]);
			$clean_phrase = $phrase_parts[0] . $clean_phrase;
		}
		if (isset($phrase_parts[1])) {
			$phrase_parts[1] = preg_replace('/[*]/', ' [\\w\']+', $phrase_parts[1]);
			$clean_phrase = $clean_phrase . $phrase_parts[1];
		}
		
		return trim($clean_phrase);
	}
	
	/**
	 * Returns the description
	 *
	 * @return string
	 */
	public function getDescription()
	{
		return $this->_data[self::IDX_DESCRIPTION];
	}
	
	/**
	 * Returns the options
	 *
	 * If $key is specified, then the value for that option will be
	 * returned. Otherwise an array of all the options will be
	 * returned.
	 *
	 * @param string $key The option key
	 * @return array
	 */
	public function getOptions($key = null)
	{
		if (null !== $key) {
			if (!isset($this->_data[self::IDX_OPTIONS][$key])) {
				return null;
			}
			return $this->_data[self::IDX_OPTIONS][$key];
		}
		return $this->_data[self::IDX_OPTIONS];
	}
	
	/**
	 * Returns the safe name of the factory that created the keyphrase
	 *
	 * @return string
	 */
	public function getFactory()
	{
		return $this->_data[self::IDX_FACTORY];
	}
	
	/**
	 * Returns the date the keyphrase was created as a unix timestamp
	 *
	 * @return int
	 */
	public function getCreated()
	{
		if (0 === $this->_data[self::IDX_CREATED]) {
			$this->_data[self::IDX_CREATED] = time();
		}
		return $this->_data[self::IDX_CREATED];
	}
	
	/**
	 * Returns the hash
	 *
	 * @return string
	 */
	public function getHash()
	{
		if (null === $this->_data[self::IDX_HASH]) {
			$this->_data[self::IDX_HASH] = $this->_generateHash();
		}
		return $this->_data[self::IDX_HASH];
	}
	
	/**
	 * Sets the phrase
	 *
	 * @param string $phrase The phrase
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setPhrase($phrase)
	{
		if (strlen($phrase) > self::MAX_LENGTH_PHRASE) {
			/**
			 * @see Alinks_Exception
			 */
			require_once('Alinks/Exception.php');
			throw new Alinks_Exception('The phrase length can not be greater than ' . self::MAX_LENGTH_PHRASE . ' characters');
		}
		
		$this->_data[self::IDX_PHRASE] = $phrase;
		$this->notify();
		return $this;
	}
	
	/**
	 * Sets the description
	 *
	 * @param string $description The description
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setDescription($description)
	{
		if (strlen($description) > self::MAX_LENGTH_DESCRIPTION) {
			/**
			 * @see Alinks_Exception
			 */
			require_once('Alinks/Exception.php');
			throw new Alinks_Exception('The description length can not be greater than ' . self::MAX_LENGTH_DESCRIPTION . ' characters');
		}
		
		$this->_data[self::IDX_DESCRIPTION] = $description;
		$this->notify();
		return $this;
	}
	
	/**
	 * Sets the options
	 *
	 * @param array $options The options
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setOptions(array $options)
	{
		$this->_data[self::IDX_OPTIONS] = array();
		foreach($options as $key => $value) {
			$this->addOption($key, $value);
		}
		$this->notify();
		return $this;
	}
	
	/**
	 * Adds an option
	 *
	 * @param string $key Option key
	 * @param string $value Option value
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function addOption($key, $value)
	{
		$this->_data[self::IDX_OPTIONS][$key] = $value;
		$this->notify();
		return $this;
	}
	
	/**
	 * Sets the safe name of the factory that created the keyphrase
	 *
	 * @param string $factory The factory safe name
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setFactory($factory)
	{
		$this->_data[self::IDX_FACTORY] = $factory;
		$this->notify();
		return $this;
	}
	
	/**
	 * Sets the date the keyphrase was created
	 *
	 * @param string|int $created The date the keyphrase was created
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setCreated($created)
	{
		if (!is_numeric($created)) {
			$created = strtotime($created);
		}
		$this->_data[self::IDX_CREATED] = $created;
		$this->notify();
		return $this;
	}
	
	/**
	 * Sets the hash
	 *
	 * @param string $hash The hash
	 * @return Alinks_Keyphrase_Abstract
	 */
	public function setHash($hash)
	{
		$this->_data[self::IDX_HASH] = $hash;
		$this->notify();
		return $this;
	}
	
	/**
	 * Returns the keyphrase data as an array
	 *
	 * @return array
	 */
	public function toArray()
	{
		if (null === $this->_data[self::IDX_HASH]) {
			$this->getHash();
		}
		if (0 === $this->_data[self::IDX_CREATED]) {
			$this->getCreated();
		}
		
		return $this->_data;
	}
	
	/**
	 * Returns the keyphrase data as a stdClass instance
	 *
	 * @return stdClass
	 */
	public function toStdClass()
	{
		$s = new stdClass();
		$s->phrase = $this->getPhrase();
		$s->description = $this->getDescription();
		$s->created = $this->getCreated();
		$s->factory = $this->getFactory();
		$s->hash = $this->getHash();

		return $s;
	}
	
	/**
	 * Implements SplSubject::attach()
	 */
	public function attach(SplObserver $observer)
	{
		$key = array_search($observer, $this->__observers);
		if (false === $key) {
			$this->__observers[] = $observer;
		}
	}
	
	/**
	 * Implements SplSubject::detach()
	 */
	public function detach(SplObserver $observer)
	{
		$key = array_search($observer, $this->__observers);
		if (false !== $key) {
			unset($this->__observers[$key]);
		}
	}
	
	/**
	 * Implements SplSubject::notify()
	 */
	public function notify()
	{
		foreach($this->__observers as $observer) {
			$observer->update($this);
		}
	}
	
	/**
	 * Generates and returns a unique hash for the keyphrase
	 *
	 * @return string
	 */
	protected function _generateHash()
	{
		$part_one 		= substr($this->getFactory(), 0, 8);
		$part_two 		= md5($this->getPhrase() . $this->getDescription());
		$part_three 	= md5(join('', $this->getOptions()));
		
		return $part_one . '-' . $part_two . '-' . $part_three;
	}
}